package chances.epg.data.ibatis;

import java.sql.SQLException;
import java.util.List;
import java.util.Properties;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.CachingExecutor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;

/**
 * 采用分页拦截器执行物理分页查询时，原生的Executor创建cacheKey时未能包含分页参数page，为了解决这个问题，创建了本拦截器，
 * 本拦截器会拦截CachingExecutor的query方法，在创建cacheKey时将分页参数page包含其中。 老规矩，签名里要拦截的类型只能是接口。
 * 
 * @author 湖畔微风
 * 
 */
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class PaginationCacheInterceptor implements Interceptor {
	private static final Log logger = LogFactory.getLog(PaginationCacheInterceptor.class);
    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Executor executorProxy = (Executor) invocation.getTarget();
        MetaObject metaExecutor = MetaObject.forObject(executorProxy, DEFAULT_OBJECT_FACTORY,
        		DEFAULT_OBJECT_WRAPPER_FACTORY,DEFAULT_REFLECTOR_FACTORY);
        // 分离代理对象链
        while (metaExecutor.hasGetter("h")) {
            Object object = metaExecutor.getValue("h");
            metaExecutor = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY,DEFAULT_REFLECTOR_FACTORY);
        }
        // 分离最后一个代理对象的目标类
        while (metaExecutor.hasGetter("target")) {
            Object object = metaExecutor.getValue("target");
            metaExecutor = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY,DEFAULT_REFLECTOR_FACTORY);
        }
        Object[] args = invocation.getArgs();
        return this.query(metaExecutor, args);
    }

    @SuppressWarnings("rawtypes")
	public <E> List<E> query(MetaObject metaExecutor, Object[] args) throws SQLException {
        MappedStatement ms = (MappedStatement) args[0];
        Object parameterObject = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        ResultHandler resultHandler = (ResultHandler) args[3];
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        Executor executor = (Executor) metaExecutor.getOriginalObject();
        
        //如果开启Cache
        if(ms.isUseCache()){
        	if (rowBounds == null || rowBounds == RowBounds.DEFAULT) {
        		return executor.query(ms, parameterObject, rowBounds, resultHandler);
        	}
        	
        	//只对分页查询进行处理
        	if (rowBounds instanceof PageRowBounds) {
        		PageRowBounds pageRowBounds = (PageRowBounds)rowBounds;
        		
        		//生成分页查询的CacheKey
        		CacheKey cacheKey = createCacheKey(ms, parameterObject, pageRowBounds, boundSql);
        		List<E> result = executor.query(ms, parameterObject, rowBounds, resultHandler, cacheKey, boundSql);
        		
        		//生成用于保存分页信息的CacheKey
        		CacheKey pageCacheKey = this.createPageCacheKey(cacheKey);
            	if(pageCacheKey == null) {
            		return result;
            	}
            	
            	Cache cs = ms.getCache();
            	if(cs != null) {
	            	PageRowBounds cacheRowBounds = (PageRowBounds)cs.getObject(pageCacheKey);
	            	if(cacheRowBounds != null) {
	            		logger.debug("get rowBounds from cache.sqlId:" + ms.getId());
	            	}
	            	
	            	//如果查询结果中，有分页信息的总记录数，则保存到缓存中
	        		if(pageRowBounds.getRecordCount() > 0) {
	        			cs.putObject(pageCacheKey, pageRowBounds);
	        		}else{
	        			//如果没有总记录数，则从缓存中获取
	        			if(cacheRowBounds != null) {
	        				pageRowBounds.setRecordCount(cacheRowBounds.getRecordCount());
	        			}
	        		}
            	}
        		return result;
        	}else{
        		return executor.query(ms, parameterObject, rowBounds, resultHandler);
        	}
        }else{
            return executor.query(ms, parameterObject, rowBounds, resultHandler);
        }
    }

    private CacheKey createCacheKey(MappedStatement ms, Object parameterObject, PageRowBounds rowBounds, BoundSql boundSql) {
        CacheKey cacheKey = new CacheKey();
        cacheKey.update(ms.getId());
        cacheKey.update(rowBounds.getPageSize());
        cacheKey.update(rowBounds.getPageNo());
        cacheKey.update(rowBounds.getStart());
        
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        cacheKey.update(boundSql.getSql());
        MetaObject metaObject = MetaObject.forObject(parameterObject,
        		DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY,DEFAULT_REFLECTOR_FACTORY);

        if (parameterMappings.size() > 0 && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                cacheKey.update(parameterObject);
            } else {
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        cacheKey.update(metaObject.getValue(propertyName));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        cacheKey.update(boundSql.getAdditionalParameter(propertyName));
                    }
                }
            }
        }
        return cacheKey;
    }
    
    private CacheKey createPageCacheKey(CacheKey queryCacheKey) {
    	try{
	        CacheKey cacheKey = queryCacheKey.clone();
	        cacheKey.update("COUNT_CacheKey");
	        return cacheKey;
    	}catch(CloneNotSupportedException ex){
    		return null;
    	}
    }
    

    /**
     * 只拦截CachingExecutor，其他的直接返回目标本身
     */
    @Override
    public Object plugin(Object target) {
        if (target instanceof CachingExecutor) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }


    @Override
    public void setProperties(Properties properties) {

    }
}
